﻿using InTheHand.Net.Sockets;
using InTheHand.Windows.Forms;
using MuseLibrary;
using MuseLibrary.Events;
using MuseLibrary.Packets;
using MuseUI.ObservableData;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using Timer = System.Timers.Timer;

namespace MuseUI
{
    public class MainPresenter
    {
        public MainPresenter(UserContext userContext)
        {
            _userContext = userContext;
            _view = new MainView(this);
            _view.Show();

            _selectedMuseChartType = _userContext.MuseChartType;
            InitialiseChartingData();

            _timerChartUpdate = new Timer() { Enabled = false, Interval = (int)Math.Ceiling(1000.0 / _userContext.ChartUpdateFrequency) };
            _timerChartUpdate.Elapsed += OnTimerChartUpdateElapsed;

            _timerPacketUpdate = new Timer() { Enabled = false, Interval = 200 };
            _timerPacketUpdate.Elapsed += OnTimerPacketCountUpdateElapsed;

            SwitchChart(UserContext.MuseChartType);

            if (!String.IsNullOrEmpty(_userContext.MuseAddress))
            {
                var timer = new Timer() { Interval = 1000, Enabled = true };
                timer.Elapsed += (s, e) =>
                    {
                        timer.Enabled = false;
                        MakeMuseConnection(_userContext.AutoConnect);
                        timer.Dispose();
                    };
            }
        }

        private void InitialiseChartingData()
        {
            _observableDataCollection = new Dictionary<MuseChartType, IObservableData>();
            _observableDataCollection.Add(MuseChartType.Eeg, new EegObservableData());
            _observableDataCollection.Add(MuseChartType.Accelerometer, new AccelerometerObservableData());
        }

        private Dictionary<MuseChartType, IObservableData> _observableDataCollection;

        private void OnTimerPacketCountUpdateElapsed(object sender, ElapsedEventArgs e)
        {
            _view.UpdatePacketsCount(_packetCounter);
        }

        private void OnTimerChartUpdateElapsed(object sender, ElapsedEventArgs e)
        {
            var series = _observableDataCollection[_selectedMuseChartType].UpdateSeries();
            _view.BeginInvoke(new MethodInvoker(() => _view.UpdateChart(series)));
        }

        internal void SwitchChart(MuseChartType type)
        {
            _view.MainChart.ChartAreas.Clear();
            _view.MainChart.Series.Clear();
            IObservableData chartingData = _observableDataCollection[type];
            _chartingOptions = _observableDataCollection[type].ChartingOptions;
            _view.BindChartingOptions();

            foreach (var chartArea in chartingData.ChartAreas)
                _view.MainChart.ChartAreas.Add(chartArea);

            foreach (var series in chartingData.AllSeries)
                _view.MainChart.Series.Add(series);
        }

        private void MakeMuseConnection(bool connect)
        {
            _museConnection = new MuseConnection(_userContext.MuseAddress, _userContext.NotchFrequency == 50);
            _museConnection.PacketReceived += OnMusePacketReceived;
            _museConnection.ConnectionStatusUpdated += OnMuseConnectionStatusUpdated;
            if (connect)
            {
                _museConnection.Connect();
            }
        }

        private void OnMuseConnectionStatusUpdated(object sender, ConnectionEventArgs e)
        {
            var statusText = "";
            Color statusColour;
            switch (e.ConnectionStatus)
            {
                case ConnectionStatus.Connecting:
                    statusText = "Connecting...";
                    statusColour = Color.FromArgb(200, 190, 225);
                    break;
                case ConnectionStatus.Connected:
                    statusText = "Connected";
                    statusColour = Color.FromArgb(190, 225, 200);
                    break;
                case ConnectionStatus.ConnectionFailed:
                    statusText = "Connection failed, retrying...";
                    statusColour = Color.FromArgb(225, 190, 200);
                    break;
                case ConnectionStatus.Disconnected:
                    statusText = "Disconnected";
                    statusColour = Color.FromArgb(225, 190, 200);
                    break;
                default:
                    throw new ArgumentOutOfRangeException(String.Format("Unknown connection status: {0}", e.ConnectionStatus.ToString()));
            }

            _view.UpdateConnectionStatus(statusText, statusColour);

            if (e.ConnectionStatus == ConnectionStatus.Connected)
            {
                _timerChartUpdate.Enabled = true;
                _timerPacketUpdate.Enabled = true;
            }
        }

        private void OnMusePacketReceived(object sender, PacketEventArgs e)
        {
            _packetCounter++;
            var now = DateTime.Now;
            
            switch (e.Packet.Type)
            {
                case PacketType.UncompressedEeg:
                    _observableDataCollection[MuseChartType.Eeg].InsertData(now, ((UncompressedEegPacket)e.Packet).Channels);
                    _lastUncompressed = now;
                    _compressedSamplesSinceLastUncompressed = 0;
                    break;
                case PacketType.CompressedEeg:
                    if (_lastUncompressed == null) 
                        return;
                    var compressedEegPacket = (CompressedEegPacket)e.Packet;

                    for (int i = 0; i < 16; i++)
                    {
                        var channels = ((CompressedEegPacket)e.Packet).Channels;
                        var data = new [] { channels[0][i], channels[1][i], channels[2][i], channels[3][i]};
                        var date = _lastUncompressed.Value.AddSeconds(++_compressedSamplesSinceLastUncompressed * _timeBetweenCompressedSamples);
                        _observableDataCollection[MuseChartType.Eeg].InsertData(date, data);
                    }
                    break;
                case PacketType.Accelerometer:
                    _observableDataCollection[MuseChartType.Accelerometer].InsertData(now, ((AccelerometerPacket)e.Packet).Channels);
                    break;
                case PacketType.Battery:
                    var batteryPercent = (Double)(((BatteryPacket)e.Packet).PercentageBatteryRemaining) / 10000.0;
                    _view.UpdateBatteryPercent(batteryPercent);
                    break;
            }
        }

        private DateTime? _lastUncompressed = null;
        private int _compressedSamplesSinceLastUncompressed = 0;
        private const Double _timeBetweenCompressedSamples = 1 / 220.0;
        private UserContext _userContext;
        private MainView _view;
        private MuseConnection _museConnection;
        private Timer _timerChartUpdate;
        private Timer _timerPacketUpdate;
        private MuseChartType _selectedMuseChartType;
        private int _packetCounter = 0;

        public MainView View
        {
            get { return _view; }
        }

        public MuseChartType SelectedMuseChartType
        {
            get { return _selectedMuseChartType; }
        }

        internal void DiscoverDevices()
        {
            var discoveryDialog = new SelectBluetoothDeviceDialog();
            if (discoveryDialog.ShowDialog() == DialogResult.Cancel) return;
            var selectedDevice = discoveryDialog.SelectedDevice;
            _userContext.MuseAddress = selectedDevice.DeviceAddress.ToString();
            MakeMuseConnection(true);
        }

        internal void SetMuseChartType(MuseChartType museChartType)
        {
            _userContext.MuseChartType = museChartType;
            _selectedMuseChartType = museChartType;
            SwitchChart(_selectedMuseChartType);
        }

        internal void CloseMuseConnection()
        {
            if (_museConnection != null)
                _museConnection.Disconnect();
            _timerChartUpdate.Enabled = false;
            _timerChartUpdate.Dispose();
            _timerPacketUpdate.Enabled = false;
            _timerPacketUpdate.Dispose();
        }

        internal void Connect()
        {
            if (String.IsNullOrEmpty(_userContext.MuseAddress))
            {
                _view.UpdateConnectionStatus("Need to discover a device first", Color.FromArgb(225, 190, 200));
                return;
            }

            MakeMuseConnection(true);
        }

        public UserContext UserContext
        {
            get { return _userContext; }
        }

        private BindingList<ObservableDataOption> _chartingOptions;
        public BindingList<ObservableDataOption> ChartingOptions
        {
            get { return _chartingOptions; }
        }

        internal void ToggleRecording()
        {
            if (!_observableDataCollection[_selectedMuseChartType].ChartingOptions.Any(x => x.IsRecording))
            {
                _view.connectionStatusLabel.Text = "Need to select series to record first";
                return;
            }

            _isRecording = !_isRecording;
            if (_isRecording)
            {
                _observableDataCollection[_selectedMuseChartType].IsRecording = true;
            }
            else
            {
                String outputPath = "";
                using (var saveFileDialog = new SaveFileDialog())
                {
                    saveFileDialog.DefaultExt = ".csv";
                    saveFileDialog.Filter = "csv files (*.csv)|*.csv";
                    saveFileDialog.FilterIndex = 0;
                    saveFileDialog.RestoreDirectory = true;
                    if (saveFileDialog.ShowDialog() != DialogResult.OK) return;
                    outputPath = saveFileDialog.FileName;
                }

                _observableDataCollection[_selectedMuseChartType].OutputRecordedData(outputPath);
            }
        }

        private bool _isRecording;
        public bool IsRecording
        {
            get { return _isRecording; }
        }

        internal void SetNotchFrequency(int frequency)
        {
            MessageType message;
            switch (frequency)
            {
                case 50:
                    message = MessageType.NotchFrequency50Hz;
                    break;
                case 60:
                    message = MessageType.NotchFrequency60Hz;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            _userContext.NotchFrequency = frequency;
            if (_museConnection != null)
                _museConnection.SendMessage(message);
        }

        internal void SetFourierChannel(int channel)
        {
            SelectedFourierChannel = channel;
            ((EegObservableData)_observableDataCollection[_selectedMuseChartType]).ChannelForFourier = channel;
        }

        public int SelectedFourierChannel { get; set; }
    }
}
